﻿using System;
using System.Drawing;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenTK;
using OpenTK.Graphics;
using System.Threading;
using Cloo;
namespace Chimp2D
{
    public class ChimpGameList : ChimpCollection<ChimpGame>
    {
        public void RunExample(int index)
        {
            this.Item[index].StartGame();
        }
        public void RunExample(string id)
        {
            this.ItemMap[id].StartGame();
        }
    }
    public class ChimpState
    {
        public bool Abort = false;
        public virtual void LoadState()
        {

        }
        public virtual void UnloadState()
        {

        }
        public virtual void UpdateState()
        {

        }
        public virtual void RenderState()
        {

        }
        public void AbortState()
        {
            Abort = true;
        }
    }
    public class ChimpGpuBuffer<T> where T : struct
    {
        public ComputeBuffer<T> Buffer;
        public int Count
        {
            get
            {
                return (int)Buffer.Count;
            }
        }
        public long CountLong
        {
            get
            {
                return Buffer.Count;
            }
        }
        public ChimpGpuBuffer(T[] data)
        {
            Buffer = new ComputeBuffer<T>(Compute.Context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.UseHostPointer, data);
        }
        public ChimpGpuBuffer()
        {

        }
        public void CreateBuffer(T[] data)
        {
            Buffer = new ComputeBuffer<T>(Compute.Context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, data);
        }
        public void UpdateBuffer(T[] data,bool block=false)
        {
            Compute.Queue.WriteToBuffer(data, Buffer,block,0,0,data.Length,null);
            //Compute.Queue.Finish();
            //Buffer.Dispose();
            //Buffer = new ComputeBuffer<T>(Compute.Context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, data);
        }
        public void UpdateBufferRange(T[] data, int offset, bool block = false)
        {
            Compute.Queue.WriteToBuffer<T>(data, Buffer,block, 0, offset, data.Length, null);
            //Compute.Queue.Finish();
        }
        public void UpdateData(ref T[] data, bool block = false)
        {
            Compute.Queue.Finish();
            Compute.Queue.ReadFromBuffer(Buffer, ref data,block, null);
            //Compute.Queue.Finish();
        }
        public void UpdateDataRange(ref T[] data, int offset, bool block = false)
        {
            Compute.Queue.ReadFromBuffer<T>(Buffer, ref data, false, offset, 0, data.Length, null);
            //Compute.Queue.Finish();
        }
    }
    public class ChimpFrameBuffer : ChimpImageBuffer
    {
        private int Texture;
        public bool NeedsUpload = true;
        public ChimpFrameBuffer(int w, int h) : base(w,h)
        {

            W = w;
            H = h;
            GL.Enable(EnableCap.Texture2D);
            GL.GenTextures(1, out Texture);
            UpdateTexture(w, h);
            GL.Disable(EnableCap.Texture2D);
        }

        
        private void UpdateTexture(int w, int h)
        {
            GL.BindTexture(TextureTarget.Texture2D, Texture);

            GL.TexEnv(TextureEnvTarget.TextureEnv, TextureEnvParameter.TextureEnvMode, (float)TextureEnvMode.Replace);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Clamp);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Clamp);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
            UpdateTextureImage(w, h);

            GL.BindTexture(TextureTarget.Texture2D, 0);
            
        }

        private void UpdateTextureImage(int w, int h)
        {

        //    DownloadImage();
            /*
            byte[] dat = new byte[w * h * 4];
            for (int x = 0; x < w; x++)
            {
                for (int y = 0; y < h; y++)
                {
                    int loc = y * w * 4 + x * 4;
                    dat[loc] = (byte)(Data[loc] * 255.0f);
                    dat[loc + 1] = (byte)(Data[loc+1] * 255.0f);
                    dat[loc + 2] = (byte)(Data[loc+2] * 255.0f);
                    dat[loc + 3] = 255;
                }
            }
             */
            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba32f, w, h, 0, PixelFormat.Rgba, PixelType.Float, Data);
        }

        public void Blit()
        {

          
                DownloadImage();

                GL.Enable(EnableCap.Texture2D);
                GL.BindTexture(TextureTarget.Texture2D, Texture);
                UpdateTextureImage((int)W,(int)H);
                GL.BindTexture(TextureTarget.Texture2D, 0);
                GL.Disable(EnableCap.Texture2D);
                NeedsUpload = false;
          

            GL.Enable(EnableCap.Texture2D);
           GL.BindTexture(TextureTarget.Texture2D,Texture);
                GL.Color4(0.0f, 1.0f, 0.0f, 1.0f);
            GL.Begin(BeginMode.Quads);

            GL.TexCoord2(0,0);
            GL.Vertex2(0,0);

            GL.TexCoord2(1,0);
            GL.Vertex2(W,0);

            GL.TexCoord2(1,1);
            GL.Vertex2(W,H);

            GL.TexCoord2(0,1);
            GL.Vertex2(0,H);

            GL.End();
            GL.BindTexture(TextureTarget.Texture2D, 0);
            GL.Disable(EnableCap.Texture2D);
        }
    };
    public static class ChimpDraw
    {
        public static ChimpFrameBuffer FB;
        public static ComputeKernel kBlitRect;
        public static ComputeKernel kBlitImage;
        public static ComputeKernel kCls;
        public static ComputeProgram kDrawProg;
        public static Vector4 col;
        public static ChimpGpuBuffer<float> ClsCol;
        public static ChimpGpuBuffer<float> Col;
        public static void Init()
        {
            FB = new ChimpFrameBuffer((int)ChimpGame.W, (int)ChimpGame.H);
            kDrawProg = new ComputeProgram(Compute.Context, System.IO.File.ReadAllText("Chimp2D.cl"));
            var buildNotify = new Cloo.Bindings.ComputeProgramBuildNotifier(CL_BuildErr);
            try
            {
                kDrawProg.Build(Compute.Context.Devices, "", null, IntPtr.Zero);
            }
            catch
            {
                Console.WriteLine("Error building Chimp2D module.\nErr:" + kDrawProg.GetBuildLog(Compute.Context.Devices[0]));
            }
         
            kBlitRect = kDrawProg.CreateKernel("ChimpBlitRect");
            kBlitImage = kDrawProg.CreateKernel("ChimpBlitImage");
            kCls = kDrawProg.CreateKernel("ChimpCls");
            
            ClsCol = new ChimpGpuBuffer<float>(new float[] { 1.0f, 0.0f, 0.0f, 1.0f });
            Col = new ChimpGpuBuffer<float>(new float[] { 1.0f, 1.0f, 1.0f, 1.0f });
           
          

        }
        public static void CL_BuildErr(Cloo.Bindings.CLProgramHandle ph,IntPtr err)
        {
            Console.WriteLine("Buili Notify:");
        }
        public static void Rect(float x, float y, float w, float h)
        {

        }
        public static void SetClsColor(Vector4 cls)
        {
            ClsCol.UpdateBuffer(new float[] { cls.X, cls.Y, cls.Z, cls.W });
            
        }
        public static void SetCol(Vector4 col)
        {
            //Compute.Queue.Finish();
            Col.UpdateBuffer(new float[] { col.X, col.Y, col.Z, col.W },true);
 
        }
        public static void BlitImage(ChimpImageBuffer img, int x, int y, int w, int h)
        {
            kBlitImage.SetMemoryArgument(0, FB.Buffer);
            kBlitImage.SetValueArgument<int>(1, (int)FB.W);
            kBlitImage.SetMemoryArgument(2, img.Buffer);
            kBlitImage.SetValueArgument<float>(3, img.W);
            kBlitImage.SetValueArgument<float>(4, img.H);
            kBlitImage.SetMemoryArgument(5, Col.Buffer);
            kBlitImage.SetValueArgument<int>(6, x);
            kBlitImage.SetValueArgument<int>(7, y);
            kBlitImage.SetValueArgument<int>(8, w);
            kBlitImage.SetValueArgument<int>(9, h);
            ComputeEventList el = new ComputeEventList();
            Compute.Queue.Execute(kBlitImage, null, new long[] { h }, null, el);
            FB.NeedsUpload = true;
            Compute.Queue.Wait(el);
        }
        public static void BlitRect(int x, int y, int w, int h)
        {
    
            kBlitRect.SetMemoryArgument(0, FB.Buffer);
            kBlitRect.SetValueArgument<int>(1, (int)FB.W);
            kBlitRect.SetMemoryArgument(2, Col.Buffer);
            kBlitRect.SetValueArgument<int>(3, x);
            kBlitRect.SetValueArgument<int>(4, y);
            kBlitRect.SetValueArgument<int>(5, w);
            kBlitRect.SetValueArgument<int>(6, h);
            //Compute.Queue.Finish();
            ComputeEventList el = new ComputeEventList();
            Compute.Queue.Execute(kBlitRect, null, new long[] { h }, null,el);
            FB.NeedsUpload = true;

            Compute.Queue.Wait(el);


        }
        public static void Cls()
        {
            
            kCls.SetMemoryArgument(0, FB.Buffer);
            kCls.SetValueArgument<int>(1, (int)FB.W);
            kCls.SetValueArgument<int>(2, (int)FB.H);
            kCls.SetMemoryArgument(3, ClsCol.Buffer);
    
            //ComputeEventList el = new ComputeEventList();

            Compute.Queue.Execute(kCls, null, new long[] { (int)(FB.H) }, null,null);
            FB.NeedsUpload = true;
            //Compute.Queue.Finish();

        }
    }
    public class ChimpImageBuffer : ChimpGpuBuffer<float>
    {
        public Bitmap OriginalBitmap;
        public float[] Data;
        public float W, H;
        public ChimpImageBuffer(int w, int h)
        {
            W = w;
            H = h;
            Data = new float[w*h*4];
            CreateBuffer(Data);
        }
        public ChimpImageBuffer(string path) : base()
        {

            OriginalBitmap = new Bitmap(path);
            W = OriginalBitmap.Width;
            H = OriginalBitmap.Height;
            float[] data = new float[OriginalBitmap.Width * OriginalBitmap.Height*4];
            for (int y = 0; y < OriginalBitmap.Height;y++ )
            {
                for (int x = 0; x < OriginalBitmap.Width; x++)
                {
                    var loc = y * OriginalBitmap.Width * 4 + x * 4;
                    var pix = OriginalBitmap.GetPixel(x, y);
                    data[loc] = (float)(pix.R)/255.0f;
                    data[loc+1] = (float)(pix.G)/255.0f;
                    data[loc + 2] = (float)(pix.B) / 255.0f;
                    data[loc + 3] = (float)(pix.A) / 255.0f;
                }
            }
            CreateBuffer(data);
            Data = data;
        }
        public Vector4 GetPixel(int x, int y,bool updateFirst=false)
        {
            int loc = y * OriginalBitmap.Width + x * 4;
            if (updateFirst)
            {
                float[] tmpData = new float[4];
                UpdateDataRange(ref tmpData,loc, false);
                Data[loc] = tmpData[0];
                Data[loc + 1] = tmpData[1];
                Data[loc + 2] = tmpData[2];
                Data[loc + 3] = tmpData[3];
            }
            return new Vector4(Data[loc], Data[loc + 1], Data[loc + 2], Data[loc + 3]);
        }
        public void UploadImage()
        {
            UpdateBuffer(Data);
        }
        public void DownloadImage()
        {
            UpdateData(ref Data, true);
        }
        public void SetPixel(int x, int y, Vector4 col, bool delayed = true)
        {
            var loc = y * OriginalBitmap.Width * 4 + x * 4;
            Data[loc] = col.X;
            Data[loc + 1] = col.Y;
            Data[loc + 2] = col.Z;
            Data[loc + 3] = col.W;
            if (!delayed)
            {
                float[] tmpData = new float[4];
                tmpData[0] = col.X;
                tmpData[1] = col.Y;
                tmpData[2] = col.Z;
                tmpData[3] = col.W;
                UpdateBufferRange(tmpData, loc,false);
            }
        }
    }
    public static class Compute
    {
        public static ComputeContext Context;
        public static ComputeCommandQueue Queue;
        public static void Init()
        {
            ComputeContextPropertyList prop = new ComputeContextPropertyList(ComputePlatform.Platforms[0]);
            Cloo.Bindings.ComputeContextNotifier notify = new Cloo.Bindings.ComputeContextNotifier(CL_Err);
            Context = new ComputeContext(ComputePlatform.Platforms[0].Devices[0].Type, prop,notify, IntPtr.Zero);
            Queue = new ComputeCommandQueue(Context, Context.Devices[0], ComputeCommandQueueFlags.None);
        }
        public static void CL_Err(string err, IntPtr p1, IntPtr p2, IntPtr p3)
        {
            throw new ChimpException(err);
        }
    }
    public class ChimpException : Exception
    {
        public ChimpException(string err) : base(err)
        {
           
        }
    }
    public class ChimpGame : GameWindow 
    {
        public void StartGame()
        {
            Console.WriteLine("Initializing Chimp Draw module.");
            Compute.Init();
            ChimpDraw.Init();

            Console.WriteLine("Initialized.");
           
            OnStartGame();
            Run();
            States.ForEach((item) =>
            {
                item.UnloadState();
            });
            States.Item.Clear();
            States.ItemMap.Clear();
            GameEnd();
        }
        public virtual void GameEnd()
        {

        }
        public string GameInfo = "Unknown Chimp Game.";
        public string CopyrightInfo = "(c)Anthony Robert Wells 2012";
        public string GameName = "Game";
        public static int GameWindowW = 256;
        public static int GameWindowH = 256;
        public static string GameWindowTitle = "Chimp2D Game (c)DreamSpace 2012";
        public static bool GameWindowFullscreen = false;
        public static float W
        {
            get { return GameWindowW; }
            set { GameWindowW = (int)value; }
        }
        public static float H
        {
            get { return GameWindowH; }
            set { GameWindowH = (int)value; }
        }
        public string Title
        {
            get { return GameWindowTitle; }
        }
        public bool FullScreen
        {
            get { return GameWindowFullscreen; }
        }
        public static ChimpState BeginState = null;
        public ChimpGame() : base(GameWindowW,GameWindowH,GraphicsMode.Default,GameWindowTitle,GameWindowFullscreen == true ? GameWindowFlags.Fullscreen : GameWindowFlags.Default,DisplayDevice.AvailableDisplays[0],2,0,GraphicsContextFlags.Default)
        {

        }
        private static ChimpCollection<ChimpState> States = new ChimpCollection<ChimpState>();
        private static ChimpCollection<ChimpState> Transients = new ChimpCollection<ChimpState>();
        public static void StartTransient(string id,ChimpState state,int updateInterval = 15)
        {
            Transients.PushLast(state,id);
            Thread th = new Thread(() => {
                state.LoadState();
                while (!state.Abort)
                {
                    state.UpdateState();
                    Thread.Sleep(updateInterval);
                }
                state.UnloadState();
            });
            th.Start();
        }
        public static void StopTransient(string id)
        {
            var t = Transients.Remove(id);
            t.AbortState();
        }
        public static void PushState(ChimpState state)
        {
            States.PushLast(state);
            state.LoadState();
        }
        public static ChimpState PopState()
        {
            States.Last.UnloadState();
            return States.PopLast();
        }
        protected override void OnLoad(EventArgs e)
        {
            ChimpDraw.FB = new ChimpFrameBuffer((int)W, (int)H);
            Matrix4 orth = Matrix4.CreateOrthographicOffCenter(0, W, H, 0, -1, 1);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadMatrix(ref orth);
            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadIdentity();
            GL.Disable(EnableCap.Blend);
            GL.Disable(EnableCap.DepthTest);
            GL.Disable(EnableCap.CullFace);
            GL.Disable(EnableCap.AlphaTest);
            GL.Disable(EnableCap.ScissorTest);
            GL.Color4(1.0f, 1.0f, 1.0f, 1.0f);
            GL.ClearColor(0.0f, 0.0f, 0.0f, 1.0f);
            GL.Viewport(0, 0, (int)W, (int)H);
        }

        private void OnStartGame()
        {
            GameStarted();
            if (BeginState != null)
            {
                PushState(BeginState);
            }
        }
        public virtual void GameStarted()
        {

        }
        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            base.OnUpdateFrame(e);
            GameUpdate();
            if (States.HasItems) States.Last.UpdateState();
        }
        public virtual void GameUpdate()
        {
        }
        public int NextFPS = Environment.TickCount + 1000;
        public int FPS = 0;
        public int Frame = 0;
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            
            base.OnRenderFrame(e);
            GameRender();
            if (States.HasItems)
            {
                States.Last.UpdateState();
            }
            
            SwapBuffers();

            Frame++;
            var t = Environment.TickCount;
            if (t > NextFPS)
            {
                FPS = Frame;
                Frame = 0;
                NextFPS = t + 1000;
                Console.WriteLine("Fps:" + FPS);
            }

      }
        public virtual void GameRender()
        {

        }
    }

}
